/*
 * This file is part of aion-lightning <aion-lightning.org>.
 *
 *  aion-lightning is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  aion-lightning is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with aion-lightning.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionengine.gameserver.services.siegeservice;

import com.aionemu.commons.callbacks.EnhancedObject;
import com.aionengine.gameserver.ai2.AbstractAI;
import com.aionengine.gameserver.configs.main.SiegeConfig;
import com.aionengine.gameserver.model.DescriptionId;
import com.aionengine.gameserver.model.gameobjects.Creature;
import com.aionengine.gameserver.model.gameobjects.player.Player;
import com.aionengine.gameserver.model.gameobjects.siege.SiegeNpc;
import com.aionengine.gameserver.model.siege.FortressLocation;
import com.aionengine.gameserver.model.siege.SiegeLocation;
import com.aionengine.gameserver.model.siege.SiegeModType;
import com.aionengine.gameserver.model.siege.SiegeRace;
import com.aionengine.gameserver.model.templates.npc.AbyssNpcType;
import com.aionengine.gameserver.network.aion.serverpackets.SM_SIEGE_LOCATION_STATE;
import com.aionengine.gameserver.services.SiegeService;
import com.aionengine.gameserver.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.Date;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author SoulKeeper, Source
 */
public abstract class Siege<SL extends SiegeLocation> {

    private static final Logger log = LoggerFactory.getLogger(Siege.class);
    private final SiegeBossDeathListener siegeBossDeathListener = new SiegeBossDeathListener(this);
    private final SiegeBossDoAddDamageListener siegeBossDoAddDamageListener = new SiegeBossDoAddDamageListener(this);
    private final AtomicBoolean finished = new AtomicBoolean();
    private final SiegeCounter siegeCounter = new SiegeCounter();
    private final SL siegeLocation;
    private boolean bossKilled;
    private SiegeNpc boss;
    private Date startTime;
    private boolean started;

    public Siege(SL siegeLocation) {
        this.siegeLocation = siegeLocation;
    }

    public final void startSiege() {

        boolean doubleStart = false;

        // keeping synchronization as minimal as possible
        synchronized (this) {
            if (started) {
                doubleStart = true;
            } else {
                startTime = new Date();
                started = true;
            }
        }

        if (doubleStart) {
            log.error("Attempt to start siege of SiegeLocation#" + siegeLocation.getLocationId() + " for 2 times");
            return;
        }

        onSiegeStart();
        //Check for Balaur Assault
        if (SiegeConfig.BALAUR_AUTO_ASSAULT) {
            BalaurAssaultService.getInstance().onSiegeStart(this);
        }
    }

    public final void startSiege(int locationId) {
        SiegeService.getInstance().startSiege(locationId);
    }

    public final void stopSiege() {
        if (finished.compareAndSet(false, true)) {
            onSiegeFinish();

            if (SiegeConfig.BALAUR_AUTO_ASSAULT) {
                BalaurAssaultService.getInstance().onSiegeFinish(this);
            }
        } else {
            log.error("Attempt to stop siege of SiegeLocation#" + siegeLocation.getLocationId() + " for 2 times");
        }
    }

    public SL getSiegeLocation() {
        return siegeLocation;
    }

    public int getSiegeLocationId() {
        return siegeLocation.getLocationId();
    }

    public boolean isBossKilled() {
        return bossKilled;
    }

    public void setBossKilled(boolean bossKilled) {
        this.bossKilled = bossKilled;
    }

    public SiegeNpc getBoss() {
        return boss;
    }

    public void setBoss(SiegeNpc boss) {
        this.boss = boss;
    }

    public SiegeBossDoAddDamageListener getSiegeBossDoAddDamageListener() {
        return siegeBossDoAddDamageListener;
    }

    public SiegeBossDeathListener getSiegeBossDeathListener() {
        return siegeBossDeathListener;
    }

    public SiegeCounter getSiegeCounter() {
        return siegeCounter;
    }

    protected abstract void onSiegeStart();

    protected abstract void onSiegeFinish();

    public void addBossDamage(Creature attacker, int damage) {
        // We don't have to add damage anymore if siege is finished
        if (isFinished())
            return;

        // Just to be sure that attacker exists.
        // if don't - dunno what to do
        if (attacker == null)
            return;

        // Actually we don't care if damage was done from summon.
        // We should threat all the damage like it was done from the owner
        attacker = attacker.getMaster();
        getSiegeCounter().addDamage(attacker, damage);
    }

    public abstract boolean isEndless();

    public abstract void addAbyssPoints(Player player, int abysPoints);

    public boolean isStarted() {
        return started;
    }

    public boolean isFinished() {
        return finished.get();
    }

    public Date getStartTime() {
        return startTime;
    }

    protected void registerSiegeBossListeners() {
        // Add hate listener - we should know when someone attacked general
        EnhancedObject eo = (EnhancedObject) getBoss().getAggroList();
        eo.addCallback(getSiegeBossDoAddDamageListener());

        // Add die listener - we should stop the siege when general dies
        AbstractAI ai = (AbstractAI) getBoss().getAi2();
        eo = (EnhancedObject) ai;
        eo.addCallback(getSiegeBossDeathListener());
    }

    protected void unregisterSiegeBossListeners() {
        // Add hate listener - we should know when someone attacked general
        EnhancedObject eo = (EnhancedObject) getBoss().getAggroList();
        eo.removeCallback(getSiegeBossDoAddDamageListener());

        // Add die listener - we should stop the siege when general dies
        AbstractAI ai = (AbstractAI) getBoss().getAi2();
        eo = (EnhancedObject) ai;
        eo.removeCallback(getSiegeBossDeathListener());
    }

    protected void initSiegeBoss() {

        SiegeNpc boss = null;

        Collection<SiegeNpc> npcs = World.getInstance().getLocalSiegeNpcs(getSiegeLocationId());
        for (SiegeNpc npc : npcs) {
            if (npc.getObjectTemplate().getAbyssNpcType().equals(AbyssNpcType.BOSS)) {

                if (boss != null) {
                    throw new SiegeException("Found 2 siege bosses for outpost " + getSiegeLocationId());
                }

                boss = npc;
            }
        }

        if (boss == null) {
            throw new SiegeException("Siege Boss not found for siege " + getSiegeLocationId());
        }

        setBoss(boss);
        registerSiegeBossListeners();
    }

    protected void spawnNpcs(int locationId, SiegeRace race, SiegeModType type) {
        SiegeService.getInstance().spawnNpcs(locationId, race, type);
    }

    protected void deSpawnNpcs(int locationId) {
        SiegeService.getInstance().deSpawnNpcs(locationId);
    }

    protected void broadcastState(SiegeLocation location) {
        SiegeService.getInstance().broadcast(new SM_SIEGE_LOCATION_STATE(location), null);
    }

    protected void broadcastUpdate(SiegeLocation location) {
        SiegeService.getInstance().broadcastUpdate(location);
    }

    protected void broadcastUpdate(SiegeLocation location, int nameId) {
        SiegeService.getInstance().broadcastUpdate(location, new DescriptionId(nameId));
    }

    protected void updateOutpostStatusByFortress(FortressLocation location) {
        SiegeService.getInstance().updateOutpostStatusByFortress(location);
    }

    protected void updateTiamarantaRiftsStatus(boolean isPreparation, boolean isSync) {
        SiegeService.getInstance().updateTiamarantaRiftsStatus(isPreparation, isSync);
    }

}